package main.model;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


/**
 * A day of stock information.
 *
 * @author Kevin Gao, Michele Reshef
 *
 */
public class Day implements Comparable<Day> {

    private float myClose;
    private float myHigh;
    private float myLow;
    private float myOpen;
    private int myVolume;
    private Date myDate = new Date();

    /**
     * Constructor using explicit parameters. Should really only be used for
     * testing purposes.
     *
     * @param date date
     * @param open opening price
     * @param close closing price
     * @param high high price
     * @param low low price
     * @param volume number of shares traded
     */
    public Day (Date date, float open, float close, float high, float low, int volume) {
        myDate = date;
        myOpen = open;
        myClose = close;
        myHigh = high;
        myLow = low;
        myVolume = volume;
    }

    /**
     * Constructor using serialized data.
     *
     * @param serialized serialized map. For example:
     *        ("date" -> "2012-10-11", "open" -> 5.00 ...)
     * @throws ParseException if serialized fields don't match up w/ actual ones
     */
    public Day (Map<String, Object> serialized) throws ParseException {
        try {
            for (Field f : getClass().getDeclaredFields()) {
                Object value = serialized.get(getNormalizedFieldName(f));
                if (value != null) {
                    f.set(this, value);
                }
            }
        }
        catch (IllegalAccessException e) {
            raiseParseError(null, e);
        }
        catch (IllegalArgumentException e) {
            raiseParseError(null, e);
        }
    }

    /**
     * Serialize instance fields.
     *
     * @throws ParseException if cannot serialize fields properly
     * @return
     */
    public Map<String, Object> serialize () throws ParseException {
        Map<String, Object> serialized = new HashMap<String, Object>();
        for (Field f : getClass().getDeclaredFields()) {
            try {
                serialized.put(getNormalizedFieldName(f), f.get(this));
            }
            catch (IllegalArgumentException e) {
                raiseParseError(getClass().getEnclosingMethod(), e);
            }
            catch (IllegalAccessException e) {
                raiseParseError(getClass().getEnclosingMethod(), e);
            }
        }
        return serialized;

    }

    /**
     * Transforms an instance variable name from myFooBar to foobar. Drops the
     * my and makes everything lower case.
     *
     * @param f field to transform name of
     * @return
     */
    private String getNormalizedFieldName (Field f) {
        // Transform myFieldName to fieldname
        return f.getName().replaceAll("^my", "").toLowerCase();
    }

    /**
     * For parsing errors, build a new parsing string w/ some base information.
     * @param method Method that the error was thrown in
     * @param e The exception thrown
     * @throws ParseException
     */
    private void raiseParseError (Method method, Exception e) throws ParseException {
        String prefix =
                (method != null) ? String.format("Error running %s", method.getName()) :
                    "Error constructing";
        String message =
                String.format("%s %s: (%s) %s", prefix, getClass().getName(), e.getClass(),
                              e.getMessage());
        throw new ParseException(message, 0);
    }

    /**
     * Getter for Date.
     *
     * @return
     */
    public Date getDate () {
        return myDate;
    }

    /**
     * Getter for Opening price.
     *
     * @return
     */
    public float getOpen () {
        return myOpen;
    }

    /**
     * Getter for Closing price.
     *
     * @return
     */
    public float getClose () {
        return myClose;
    }

    /**
     * Getter for high price.
     *
     * @return
     */
    public float getHigh () {
        return myHigh;
    }

    /**
     * Getter for low price.
     *
     * @return
     */
    public float getLow () {
        return myLow;
    }

    /**
     * Getter for share volume
     *
     * @return
     */
    public int getVolume () {
        return myVolume;
    }

    @Override
    public int compareTo (Day otherDay) {
        return getDate().compareTo(otherDay.getDate());
    }
}
